package com.silence.gmall.util;

import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

/**
 * MD5加密工具类
 * 提供字符串MD5加密功能，支持多种格式输出
 * 
 * @author Silence
 * @version 1.0
 * @since 1.0
 */
@Slf4j
public final class MD5 {

    /**
     * 十六进制字符数组
     */
    private static final char[] HEX_CHARS = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

    /**
     * 十六进制字符数组（大写）
     */
    private static final char[] HEX_CHARS_UPPER = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'A', 'B', 'C', 'D', 'E', 'F'
    };

    /**
     * MD5算法名称
     */
    private static final String MD5_ALGORITHM = "MD5";

    /**
     * 私有构造器，防止实例化工具类
     */
    private MD5() {
        throw new UnsupportedOperationException("工具类不支持实例化");
    }

    /**
     * 对字符串进行MD5加密（小写十六进制）
     *
     * @param input 待加密的字符串，不能为null
     * @return MD5加密后的32位小写十六进制字符串
     * @throws IllegalArgumentException 如果input为null
     */
    public static String encrypt(String input) {
        return encrypt(input, false);
    }

    /**
     * 对字符串进行MD5加密
     *
     * @param input     待加密的字符串，不能为null
     * @param uppercase 是否返回大写十六进制，true：大写，false：小写
     * @return MD5加密后的32位十六进制字符串
     * @throws IllegalArgumentException 如果input为null
     */
    public static String encrypt(String input, boolean uppercase) {
        Objects.requireNonNull(input, "待加密字符串不能为null");
        
        try {
            byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
            return bytesToHex(calculateMD5(inputBytes), uppercase);
        } catch (Exception e) {
            log.error("MD5加密失败，输入: {}", input, e);
            throw new RuntimeException("MD5加密失败", e);
        }
    }

    /**
     * 对字节数组进行MD5加密
     *
     * @param input 待加密的字节数组，不能为null
     * @return MD5加密后的32位小写十六进制字符串
     * @throws IllegalArgumentException 如果input为null
     */
    public static String encrypt(byte[] input) {
        return encrypt(input, false);
    }

    /**
     * 对字节数组进行MD5加密
     *
     * @param input     待加密的字节数组，不能为null
     * @param uppercase 是否返回大写十六进制，true：大写，false：小写
     * @return MD5加密后的32位十六进制字符串
     * @throws IllegalArgumentException 如果input为null
     */
    public static String encrypt(byte[] input, boolean uppercase) {
        Objects.requireNonNull(input, "待加密字节数组不能为null");
        
        try {
            return bytesToHex(calculateMD5(input), uppercase);
        } catch (Exception e) {
            log.error("MD5加密失败", e);
            throw new RuntimeException("MD5加密失败", e);
        }
    }

    /**
     * 验证字符串的MD5值是否匹配
     *
     * @param input    原始字符串，不能为null
     * @param md5Hash  待验证的MD5哈希值，不能为null
     * @return true：匹配，false：不匹配
     * @throws IllegalArgumentException 如果任一参数为null
     */
    public static boolean verify(String input, String md5Hash) {
        Objects.requireNonNull(input, "原始字符串不能为null");
        Objects.requireNonNull(md5Hash, "MD5哈希值不能为null");
        
        String calculatedHash = encrypt(input);
        return calculatedHash.equalsIgnoreCase(md5Hash);
    }

    /**
     * 获取字符串的MD5哈希值（字节数组形式）
     *
     * @param input 待加密的字符串，不能为null
     * @return MD5哈希值的字节数组
     * @throws IllegalArgumentException 如果input为null
     */
    public static byte[] getHashBytes(String input) {
        Objects.requireNonNull(input, "待加密字符串不能为null");
        
        byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
        return calculateMD5(inputBytes);
    }

    /**
     * 计算字节数组的MD5哈希值
     *
     * @param input 输入字节数组
     * @return MD5哈希值字节数组
     * @throws RuntimeException 如果MD5算法不可用
     */
    private static byte[] calculateMD5(byte[] input) {
        try {
            MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
            return md.digest(input);
        } catch (NoSuchAlgorithmException e) {
            log.error("MD5算法不可用", e);
            throw new RuntimeException("MD5算法不可用", e);
        }
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param bytes     字节数组
     * @param uppercase 是否使用大写字母
     * @return 十六进制字符串
     */
    private static String bytesToHex(byte[] bytes, boolean uppercase) {
        char[] hexChars = uppercase ? HEX_CHARS_UPPER : HEX_CHARS;
        char[] result = new char[bytes.length * 2];
        
        for (int i = 0; i < bytes.length; i++) {
            int value = bytes[i] & 0xFF;
            result[i * 2] = hexChars[value >>> 4];
            result[i * 2 + 1] = hexChars[value & 0x0F];
        }
        
        return new String(result);
    }
}
